using Nop.Core;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Logistics;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Shipping;
using Nop.Services.Shipping.Tracking;
using System;
using System.Collections.Generic;

namespace Nop.Services.Logistics
{
    public interface ILogisticsService
    {
        #region Logistics Courier

        void InsertCourier(Customer customer, int[] districtIds);
        void UpdateCourier(LogisticsCourier courier);
        void DeleteCourier(LogisticsCourier courier);
        LogisticsCourier GetCourierById(int courierId);
        List<LogisticsCourier> GetPlannedCouriers(District district = null, PlanStatus planStatus = PlanStatus.Locked);
        List<LogisticsCourier> GetCouriers(District district);

        #endregion

        #region Package & Shipment

        IPagedList<LogisticsPackage> GetPendingPlanPackages(ShippingStatus[] shippingStatuses, District district, int[] timeSpans, bool includeMaxDeliveryAttempt = true, LogisticsWarehouse warehouse = null, int pageIndex = 0, int pageSize = int.MaxValue);
        IPagedList<LogisticsPackage> GetPendingPlanPackagesByZone(int[] zoneIds, int[] orderKind, ShippingStatus[] shippingStatuses, District district, int[] timeSpans, bool includeMaxDeliveryAttempt = true, LogisticsWarehouse warehouse = null, int pageIndex = 0, int pageSize = int.MaxValue);
        LogisticsPackage GetPackageById(int id);
        LogisticsPackage GetPackageByShipmentId(int shipmentId);
        List<LogisticsPackage> GetPackagesByShipmentIds(int[] shipmentIds);
        List<LogisticsPackage> GetPackagesByPickupLocation(int planId, LogisticsLocation location, bool countRemainingOnly = false);
        List<LogisticsPackage> GetPackagesByDeliveryLocation(int planId, LogisticsLocation location, bool countRemainingOnly = false);
        List<LogisticsPackage> GetPackagesToWarehouse(LogisticsPlan plan, LogisticsWarehouse warehouse);
        List<LogisticsPackage> GetPackagesByPlanId(int planId);
        LogisticsPackage GetPlanNextPackage(int planId, int currentPackageId);
        List<LogisticsPackage> GetPlanSkipPackages(int planId);
        void UpdatePackage(LogisticsPackage package, bool createPackageNote = true);
        void UpdateLogisticsPackage(LogisticsPackage package, PlanNoteStatus noteStatus, int actorCustomerId, bool displayToCourier = true, bool validForCommission = false, bool createLog = false);
        void DeleteLogisticsPackage(int packageId);

        /// <summary>
        /// Compute elapsed time span between a reference time and a package's ordered time
        /// </summary>
        /// <param name="id">Package ID</param>
        /// <param name="referenceTimeUtc">Optional reference datetime in UTC. Default is current time in UTC.</param>
        /// <returns>The elapsed timespan</returns>
        TimeSpan GetPackageElapsedTimeSinceOrdered(int id, DateTime? referenceTimeUtc = null);
        TimeSpan GetPackageElapsedTimeSinceOrdered(LogisticsPackage pkg, DateTime? referenceTimeUtc = null);

        /// <summary>
        /// Decide if a user (Customer) can change a package's status.
        /// </summary>
        /// <param name="pkg">Logistics Package</param>
        /// <param name="customer">User</param>
        /// <returns>Package Status Updateable boolean flag</returns>
        bool PackageStatusUpdateable(LogisticsPackage pkg, Customer customer);




        IPagedList<LogisticsPackage> SearchTrackingPackagesByCustomerId(
            int customerId,
            DateTime? startTime,
            DateTime? endTime,
            string trackingNumber,
            int pageIndex, int pageSize, bool includeDelivered = false);

        IPagedList<LogisticsPackage> SearchPackagesByCustomerId(
                    int applicatorId,
                    DateTime? startTime,
                    DateTime? endTime,
                    ShippingStatus[] shippingStatuses,
                    string postalCode,
                    string trackingNumber,
                    int? districtId,
                    int? pickupDistrictId,
                    int? deliveryDistrictId,
                    Zone pickupZone,
                    Zone deliveryZone,
                    AddressType? pickupAddressType,
                    AddressType? deliveryAddressType,
                    int[] OrderKindId,
                    int pageIndex, int pageSize);
        /// <summary>
        /// Search packages
        /// </summary>
        /// <param name="applicatorId"></param>
        /// <param name="startTime"></param>
        /// <param name="endTime"></param>
        /// <param name="shippingStatuses"></param>
        /// <param name="postalCode"></param>
        /// <param name="trackingNumber"></param>
        /// <param name="pickupDistrictId"></param>
        /// <param name="deliveryDistrictId"></param>
        /// <param name="pickupZone"></param>
        /// <param name="deliveryZone"></param>
        /// <param name="pickupAddressType"></param>
        /// <param name="deliveryAddressType"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        IPagedList<LogisticsPackage> SearchPackages(
            int applicatorId,
            DateTime? startTime,
            DateTime? endTime,
            ShippingStatus[] shippingStatuses,
            string postalCode,
            string trackingNumber,
            int? districtId,
            int? pickupDistrictId,
            int? deliveryDistrictId,
            Zone pickupZone,
            Zone deliveryZone,
            AddressType? pickupAddressType,
            AddressType? deliveryAddressType,
            int[] OrderKindId,
            int pageIndex, int pageSize);

        /// <summary>
        /// Search packages for tracking
        /// </summary>
        /// <param name="applicatorId"></param>
        /// <param name="startTime"></param>
        /// <param name="endTime"></param>
        /// <param name="trackingNumber"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <param name="includeDelivered">include delivered</param>
        /// <returns></returns>
        IPagedList<LogisticsPackage> SearchTrackingPackages(
            int applicatorId,
            DateTime? startTime,
            DateTime? endTime,
            string trackingNumber,
            int pageIndex, int pageSize, bool includeDelivered = false);

        /// <summary>
        /// Gets the shipment by tracking number
        /// </summary>
        /// <param name="trackingNumber">Tracking number</param>
        /// <returns></returns>
        Shipment GetShipmentByTrackingNumber(string trackingNumber);

        /// <summary>
        /// Use to create tracking number and create the package object in ths logsitics system.
        /// </summary>
        /// <param name="applicationId">The customer ID who creates the shipment.</param>
        /// <param name="fromAddr">The from address</param>
        /// <param name="toAddr">The to address</param>
        /// <param name="timeWindowIds">Time window, reserved.</param>
        /// <param name="shipment">The shipment object</param>
        /// <para name="orderKindId">order kind</para>
        /// <param name="readyTime">The ready pickup time</param>
        /// <param name="readyTime">The pickup end time</param>
        /// <returns>The package id</returns>
        int CreateShipment(int applicationId, Address fromAddr, Address toAddr, int[] timeWindowIds, Shipment shipment, int orderKindId, DateTime? readyTime = null, DateTime? pickupEndTime = null);

        bool PackageAtPickupLocation(LogisticsPackage package);
        bool PackageInWarehouse(LogisticsPackage package, bool checkStatusOnly = false);

        bool PackagePickedUp(LogisticsPackage package);

        bool PackageInTransientState(LogisticsPackage package);
        int PackagesInTransientState(List<LogisticsPackage> pkgList);

        bool PackageInStaticStateButStillInProcess(LogisticsPackage package);
        bool PackageOnWayToWarehouse(LogisticsPackage package);

        bool PackagePickupAttempted(LogisticsPackage package);
        bool PackageDeliveryAttempted(LogisticsPackage package);

        LogisticsPlan PackageLatestPlan(LogisticsPackage package);

        LogisticsLocation PackageAssociatedLocation(LogisticsPackage package);

        Address PackageAssociatedAddress(LogisticsPackage package);

        bool ValidForCommission(LogisticsPackage package, ShippingStatus pkgPreviousStatus);

        bool PackageRanked(LogisticsPackage package, int planId);

        bool PackagesSameDistrict(IList<LogisticsPackage> packages);

        bool PackagesInSameWarehouse(IList<LogisticsPackage> packages, out int theWarehouseId);

        /// <summary>
        /// Whether the package reaches the max delivery attempt
        /// </summary>
        /// <param name="package"></param>
        /// <returns></returns>
        bool IsMaxDeliveryAttempt(LogisticsPackage package);

        /// <summary>
        /// get shipmentdate and timerange
        /// </summary>
        /// <param name="orderKind">orderKind Num</param>
        /// <param name="addressType">address type</param>
        /// <param name="zipPostalCode">zipPostalCode</param>
        /// <returns></returns>
        DatePickerModel GetPickerModel(int orderKind, AddressType addressType, string zipPostalCode, DateTime? inputDate = null);

        /// <summary>
        /// get early validate day to ship product
        /// </summary>
        /// <param name="orderKind">orderKind Num</param>
        /// <param name="addressType">address type</param>
        /// <param name="zipPostalCode">zipPostalCode</param>
        /// <returns></returns>
        IList<Day> GetValidateDays(int orderKind, AddressType addressType, string zipPostalCode);

        /// <summary>
        /// Get PickUp time from GenericAttribute
        /// </summary>
        /// <param name="orderKind">orderkind</param>
        /// <returns>pick date and timeswindow id</returns>
        string GetPickUpTimeFromGenericAttribute(OrderKind orderKind);

        /// <summary>
        /// Get PickUp time show
        /// </summary>
        /// <param name="date">date</param>
        ///  <param name="order">order</param>
        /// <param name="timespanId">itmespan</param>
        /// <returns></returns>
        string GetShowPickUpTime(DateTime? date, int? timespanId, Order order);

        #endregion

        #region Logistics Plan

        List<LogisticsPlan> GetPlans(PlanStatus[] planStatusArray,
                                     DateTime? startDate,
                                     DateTime? endDate,
                                     int? driverId);
        LogisticsPlan GetPlanByDriverId(int driverId);
        LogisticsPlan GetPlanByPlanId(int planId);
        LogisticsPlan CreateLogisticsPlan(LogisticsCourier courier, LogisticsWarehouse warehouse, int actorCustomerId);
        void AssignLogisticsPlanToPackages(int[] packageIds, int planId, int customerId);
        void RemovePackagesFromLogisticsPlan(int[] packageIds, int planId, int actorCustomerId);
        bool LogisticsPlanCompleted(int planId);
        void MarkLogisticsPlanAsCompleted(int planId, int customerId);
        void UpdateLogisticsPlan(LogisticsPlan plan, int actorCustomerId);
        IList<string> ValidateLogisticsPlan(LogisticsPlan plan);
        bool PlanAccessibleToDistrict(LogisticsPlan plan, District district);

        #endregion

        #region Logistics Location & Warehouse

        /// <summary>
        /// Get a list of Logistics Locations associated with a Plan ID.
        /// Assumes that the locations have all been properly tagged with Plan ID. Otherwise,
        /// those locations will not be part of the returned list!.
        /// The logistics locations will be ordered in descending order of Rank value.
        /// </summary>
        /// <param name="planId">Plan Id</param>
        /// <returns>List of logistics locations</returns>
        List<LogisticsLocation> GetLogisticsLocationsByPlanId(int planId);

        LogisticsLocation GetLogisticsLocationById(int locationId);
        void UpdateLogisticsLocation(LogisticsLocation location);

        /// <summary>
        /// Gets all warehouses within a district
        /// </summary>
        /// <param name="district">null indicates to get all warehouses</param>
        /// <returns></returns>
        List<LogisticsWarehouse> GetAllWarehouses(District district);
        List<LogisticsWarehouse> GetAllWarehousesInRegion(Region region);
        LogisticsWarehouse GetWarehouseById(int id);
        void AddWarehouse(LogisticsWarehouse warehouse);

        LogisticsWarehouse PackageInThisWarehouse(LogisticsPackage package);

        /// <summary>
        /// Returns the proper origin location of a package (in the context of package routing)
        /// based on a package's shipping current state.
        /// </summary>
        /// <param name="package">Package</param>
        /// <param name="plan">Plan the package is in.</param>
        /// <returns>Logistics location. NULL if package's origin is considered from a Warehouse</returns>
        LogisticsLocation originLocationRouteInfo(LogisticsPackage package, LogisticsPlan plan);

        /// <summary>
        /// To check whether a set of locations can used to build a valid plan (i.e. no mixed within- and cross-district)
        /// </summary>
        /// <param name="locationIds">Location Ids (positive is valid Id, negative means warehouse (rather than location) Id</param>
        /// <returns>bool. True if valid.</returns>
        bool LocationsValidForPlan(IList<int> locationIds);

        #endregion

        #region Pending Ready

        void CheckReadyPackage();
        void MarkPackageAsReady(LogisticsPackage package, int userId);

        List<ShippingTimeWindow> GetAllShippingTimeWindows();

        ShippingTimeWindow GetShippingTimeWindowById(int id);

        bool IsEarliestShippingDate(Order order);

        #endregion

        #region Zone & Region

        // Zone
        void UpdateZone(Zone zone);
        void InsertZone(Zone zone);
        void DeleteZoneByName(string zoneName);
        Zone GetZoneByName(string zoneName);
        Zone GetZoneByZipPostalCode(string zipPostalCode, string countryCode);
        IList<Zone> GetAllZones(string countryCode);

        // Region
        IList<Region> GetAllRegions();
        Region GetRegionById(int id);
        void InsertRegion(Region region);
        void UpdateRegion(Region region);
        void DeleteRegion(Region region);

        Region GetRegionByZipPostalCode(string zipPostalCode);
        District GetDistrictByZipPostalCode(string zipPostalCode);
        bool IsZipPostalCodeInRegion(Region region, string zipPostalCode);
        IList<StateProvince> GetSupportedStateProvinces(Region region);

        // District
        IList<District> GetAllDistricts();
        IList<District> GetAllDistrictsInRegion(Region region);
        District GetDistrictById(int id);
        void InsertDistrict(District district);
        void UpdateDistrict(District district);
        void DeleteDistrict(District district);

        // Zone group
        IList<ZoneGroup> GetAllZoneGroups(District district);
        ZoneGroup GetZoneGroupById(int id);
        void InsertZoneGroup(ZoneGroup zoneGroup);
        void UpdateZoneGroup(ZoneGroup zoneGroup);
        void DeleteZoneGroup(ZoneGroup zoneGroup);

        #endregion

        #region PlanNote

        void CreatePlanNote(PlanNoteStatus planNoteStatus, int planId, PlanStatus planStatus, int customerId, bool validForCommission, bool displayToCourier,
                            int? packageId, ShippingStatus? packageStatus, int? locationId, Address address);
        void CreatePlanNote(PlanNote planNote);
        void UpdatePlanNote(PlanNote planNote);
        PlanNote GetPlanNoteById(int planNoteId);
        ICollection<PlanNote> GetPlanNotesByPlanId(int planId);
        ICollection<PlanNote> GetPlanNotesByPackageId(int packageId, int? planId = null);
        ICollection<PlanNote> GetPlanNotesForComissionByPlanId(int planId);

        ICollection<LogisticsPackage> GetPackagesFromPlanNotesByPlanId(int planId);

        IPagedList<PlanNote> SearchPlanNotes(DateTime? startDate,
                                             DateTime? endDate,
                                             int? driverId,
                                             bool aboutPackagesOnly,
                                             int pageIndex, int pageSize);
        #endregion

        #region PackageNote

        void CreatePackageNote(int packageId, PackageNoteType noteType, string noteContent, int userId, bool displayToCustomer,
                               ShippingStatus? shippingStatus, Address address);

        void CreatePackageStatusChangeNote(LogisticsPackage package, bool displayToCustomer, Address address);

        void CreatePackageNote(PackageNote packageNote);
        void UpdatePackageNote(PackageNote packageNote);

        PackageNote GetPackageNoteById(int noteId);
        ICollection<PackageNote> GetPackageNotesByPackageId(int packageId,
                                                            int? numberOfNotes = null,
                                                            PackageNoteType[] noteTypes = null,
                                                            bool? displayToCustomer = null,
                                                            int? userId = null,
                                                            bool shippingStatusOnly = false);


        /// <summary>
        /// Adapting the package notes or the plan notes to shipment status events by package
        /// </summary>
        /// <param name="package">package</param>
        /// <param name="customerOnly">populate the display-to-customer events only</param> 
        /// <returns>Shipment status events</returns>
        IList<ShipmentStatusEvent> GetPackageEvents(LogisticsPackage package, bool customerOnly = true);

        /// <summary>
        /// Gets the count of failed delivery events
        /// </summary>
        /// <param name="package"></param>
        /// <returns></returns>
        int GetFailedDeliveryEventCount(LogisticsPackage package);

        #endregion

        #region Address

        /// <summary>
        /// Gets all addresses which types are inconclusive.
        /// </summary>
        /// <param name="districtId">district</param>
        /// <param name="pageIndex">page index</param>
        /// <param name="pageSize">page size</param>
        /// <returns></returns>
        PagedList<Address> GetAllTypeInconclusiveAddresses(int? districtId, int pageIndex, int pageSize);

        #endregion

        #region Package note template

        /// <summary>
        /// Gets the template by identifier
        /// </summary>
        /// <param name="packageNoteTemplateId"></param>
        /// <returns></returns>
        PackageNoteTemplate GetPackageNoteTemplateById(int packageNoteTemplateId);

        /// <summary>
        /// Updates the template
        /// </summary>
        /// <param name="packageNoteTemplate"></param>
        void UpdatePackageNoteTemplate(PackageNoteTemplate packageNoteTemplate);

        /// <summary>
        /// Inserts a template
        /// </summary>
        /// <param name="packageNoteTemplate"></param>
        void InsertPackageNoteTemplate(PackageNoteTemplate packageNoteTemplate);

        /// <summary>
        /// Deletes a template
        /// </summary>
        /// <param name="packageNoteTemplate"></param>
        void DeletePackageNoteTemplate(PackageNoteTemplate packageNoteTemplate);

        /// <summary>
        /// Gets all templates by the reference identifier
        /// </summary>
        /// <param name="referenceId"></param>
        /// <param name="publishedOnly"></param>
        /// <returns></returns>
        List<PackageNoteTemplate> GetPackageNoteTemplates(int? referenceId = null, bool publishedOnly = true);

        /// <summary>
        /// Gets all templates by the status
        /// </summary>
        /// <param name="packageStatus"></param>
        /// <returns></returns>
        List<PackageNoteTemplate> GetPackageNoteTemplatesByStatus(ShippingStatus packageStatus);

        /// <summary>
        /// Gets the package note content by standard template or loaded template
        /// </summary>
        /// <param name="template"></param>
        /// <param name="languageId"></param>
        /// <returns></returns>
        string GetPackageNoteContentByTemplate(PackageNoteTemplate template, int languageId);

        /// <summary>
        /// Gets the package note content by template system name
        /// </summary>
        /// <param name="systemName"></param>
        /// <param name="languageId"></param>
        /// <returns></returns>
        string GetPackageNoteContentByName(string systemName, int languageId);

        /// <summary>
        /// Gets the package note content by status
        /// </summary>
        /// <param name="status"></param>
        /// <param name="languageId"></param>
        /// <returns></returns>
        string GetPackageNoteContentByStatus(ShippingStatus status, int languageId);

        #endregion
        List<string> GetCustomerRole(List<int> MappingRoleId);
    }
}
